#include "EpilNumberObject.h"
#include "EpilObjectHelper.h"
#include "EpilInterpreter.h"
#include "EpilInterpreterException.h"
#include <cmath>
#include <limits>

using namespace std;


EpilNumberObject::EpilNumberObject( EpilInterpreter * interpreter, double value )
: EpilValueObject( interpreter )
, m_value( value )
{
}


EpilNumberObject::~EpilNumberObject()
{
}


EpilObject * EpilNumberObject::OperateUnary( EpilUnaryOperator op )
{
	switch( op )
	{
	case EUO_Opposite:
		return this->GetInterpreter()->CreateNumberObject( - this->GetValue() );
	case EUO_Length:
		throw EpilInterpreterException( t("Attempt to get length of a number value.") );
	case EUO_Not:
		return this->GetInterpreter()->CreateBooleanObject( false );
	}
	throw EpilInterpreterException( t("Invalid unary operator detected.") );
}


EpilObject * EpilNumberObject::OperateBinary( EpilBinaryOperator op, EpilObject * that )
{
	switch( that->GetType() )
	{
	case EOT_Nil:
		{
			EpilNilObject * thatNil = static_cast<EpilNilObject*>( that );
			return this->OperateBinaryInternal( op, thatNil );
		}
	case EOT_Boolean:
		{
			EpilBooleanObject * thatBoolean = static_cast<EpilBooleanObject*>( that );
			return this->OperateBinaryInternal( op, thatBoolean );
		}
	case EOT_Number:
		{
			EpilNumberObject * thatDouble = static_cast<EpilNumberObject*>( that );
			return this->OperateBinaryInternal( op, thatDouble );
		}
	case EOT_String:
		{
			EpilStringObject * thatStr = static_cast<EpilStringObject*>( that );
			return this->OperateBinaryInternal( op, thatStr );
		}
	}
	return NULL;
}


EpilObject * EpilNumberObject::OperateBinaryInternal( EpilBinaryOperator op, EpilNilObject * that )
{
	switch( op )
	{
	case EBO_And:
		return this->GetInterpreter()->CreateNilObject();
	case EBO_Or:
		return this->CreateCopy();
	case EBO_Equal:
		return this->GetInterpreter()->CreateBooleanObject( false );
	case EBO_Unequal:
		return this->GetInterpreter()->CreateBooleanObject( true );
	default:
		break;
	}

	if( EpilObjectHelper::IsComparisonOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to compare number with nil.") );
	}

	if( EpilObjectHelper::IsArithmeticOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to perform arithmetic on a nil value.") );
	}

	if( op == EBO_StringConcatenate )
	{
		throw EpilInterpreterException( t("Attempt to concatenate a nil value.") );
	}

	throw EpilInterpreterException( t("Invalid binary operator detected.") );
}


EpilObject * EpilNumberObject::OperateBinaryInternal( EpilBinaryOperator op, EpilBooleanObject * that )
{
	switch( op )
	{
	case EBO_And:
		return that->CreateCopy();
	case EBO_Or:
		return this->CreateCopy();
	case EBO_Equal:
		return this->GetInterpreter()->CreateBooleanObject( false );
	case EBO_Unequal:
		return this->GetInterpreter()->CreateBooleanObject( true );
	default:
		break;
	}

	if( EpilObjectHelper::IsComparisonOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to compare number with boolean.") );
	}

	if( EpilObjectHelper::IsArithmeticOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to perform arithmetic on a boolean value.") );
	}

	if( op == EBO_StringConcatenate )
	{
		throw EpilInterpreterException( t("Attempt to concatenate a boolean value.") );
	}

	throw EpilInterpreterException( t("Invalid binary operator detected.") );
}


EpilObject * EpilNumberObject::OperateBinaryInternal( EpilBinaryOperator op, EpilNumberObject * that )
{
	switch( op )
	{
	case EBO_Add:
		return this->GetInterpreter()->CreateNumberObject( this->GetValue() + that->GetValue() );
	case EBO_And:
		return that->CreateCopy();
	case EBO_Divide:
		return this->GetInterpreter()->CreateNumberObject( this->GetValue() / that->GetValue() );
	case EBO_Equal:
		{
			bool isEqual = EpilObjectHelper::IsDoubleEquals( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateBooleanObject( isEqual );
		}
	case EBO_GreaterThan:
		return this->GetInterpreter()->CreateBooleanObject( this->GetValue() > that->GetValue() );
	case EBO_GreaterThanOrEqual:
		{
			bool isGreaterThanOrEqual = EpilObjectHelper::IsDoubleGreaterThanOrEquals( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateBooleanObject( isGreaterThanOrEqual );
		}
	case EBO_LessThan:
		return this->GetInterpreter()->CreateBooleanObject( this->GetValue() < that->GetValue() );
	case EBO_LessThanOrEqual:
		{
			bool isLessThanOrEqual = EpilObjectHelper::IsDoubleLessThanOrEquals( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateBooleanObject( isLessThanOrEqual );
		}
	case EBO_Minus:
		return this->GetInterpreter()->CreateNumberObject( this->GetValue() - that->GetValue() );
	case EBO_Mod:
		{
			double result = EpilObjectHelper::GetDoubleMod( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateNumberObject( result );
		}
	case EBO_Multiply:
		return this->GetInterpreter()->CreateNumberObject( this->GetValue() * that->GetValue() );
	case EBO_Or:
		return this->CreateCopy();
	case EBO_Power:
		{
			double result = pow( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateNumberObject( result );
		}
	case EBO_StringConcatenate:
		{
			tstring leftStr = EpilObjectHelper::ToString( this->GetValue() );
			tstring rightStr = EpilObjectHelper::ToString( that->GetValue() );
			return this->GetInterpreter()->CreateStringObject( leftStr + rightStr );
		}
	case EBO_Unequal:
		{
			bool isEqual = EpilObjectHelper::IsDoubleEquals( this->GetValue(), that->GetValue() );
			return this->GetInterpreter()->CreateBooleanObject( ! isEqual );
		}
	default:
		throw EpilInterpreterException( t("Invalid binary operator detected.") );
	}
}


EpilObject * EpilNumberObject::OperateBinaryInternal( EpilBinaryOperator op, EpilStringObject * that )
{
	switch( op )
	{
	case EBO_And:
		return that->CreateCopy();
	case EBO_Or:
		return this->CreateCopy();
	case EBO_Equal:
		return this->GetInterpreter()->CreateBooleanObject( false );
	case EBO_Unequal:
		return this->GetInterpreter()->CreateBooleanObject( true );
	default:
		break;
	}

	if( EpilObjectHelper::IsComparisonOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to compare number with string.") );
	}

	if( EpilObjectHelper::IsArithmeticOperator( op ) )
	{
		throw EpilInterpreterException( t("Attempt to perform arithmetic on a string value.") );
	}

	if( op == EBO_StringConcatenate )
	{
		return this->GetInterpreter()->CreateStringObject( EpilObjectHelper::ToString( this->GetValue() ) + that->GetValue() );
	}

	throw EpilInterpreterException( t("Invalid binary operator detected.") );
}


bool EpilNumberObject::operator==( double rhs ) const
{
	return EpilObjectHelper::IsDoubleEquals( m_value, rhs );
}


bool EpilNumberObject::operator==( const EpilObject & rhs ) const
{
	if( rhs.GetType() != EOT_Number )
	{
		return false;
	}
	const EpilNumberObject & obj = static_cast<const EpilNumberObject &>( rhs );
	return *this == obj;
}


bool EpilNumberObject::operator==( const EpilNumberObject & rhs ) const
{
	return EpilObjectHelper::IsDoubleEquals( m_value, rhs.GetValue() );
}


EpilValueObject * EpilNumberObject::Clone() const
{
	EpilNumberObject * ret = this->GetInterpreter()->CreateNumberObject( m_value );
	return ret;
}


tstring EpilNumberObject::ToString() const
{
	return EpilObjectHelper::ToString( m_value );
}